﻿using System;
using System.Data;
using System.Globalization;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace NazTek.Extension.Clr4
{
    /// <summary>
    /// Validates parameters
    /// </summary>
    public static class Parameter
    {
        /// <summary>
        /// Ensures a DataSet is not null and meets DataTable count requirements, throwing exception when invalid.
        /// </summary>
        /// <param name="name">Parameter name.</param>
        /// <param name="value">Parameter value.</param>
        /// <param name="requiresAtLeastOneTable">When set true and the DataSet must have at least one DataTable to be valid.</param>
        /// <exception cref="ArgumentOutOfRangeException"/>
        public static void Validate(string name, DataSet value, bool requiresAtLeastOneTable)
        {
            if (!IsValid(value, requiresAtLeastOneTable))
                throw new ArgumentOutOfRangeException(name, "At least one DataTable is required");
        }
        /// <summary>
        /// Determines if a DataSet is not null and meets DataTable count requirements.
        /// </summary>
        /// <param name="value">Parameter value.</param>
        /// <param name="requiresAtLeastOneTable">When set true and the DataSet must have at least one DataTable to be valid.</param>
        /// <returns>True when valid, e.g., not null and has at least one DataTable if required by specification.</returns>
        public static bool IsValid(DataSet value, bool requiresAtLeastOneTable)
        {
            return IsValid<DataSet>(value) && (requiresAtLeastOneTable ? value.Tables.Count > 0 : true);
        }
        /// <summary>
        /// Ensures string value is not null, empty, or white space, throwing exception when invalid.
        /// </summary>
        /// <param name="name">Parameter name.</param>
        /// <param name="value">Parameter value.</param>
        /// <param name="ex">Exception to be thrown, if invalid.</param>
        /// <exception cref="ArgumentNullException"/>
        public static void Validate(string name, string value, Exception ex = null)
        {
            if (!IsValid(value))
            {
                if (ex != null)
                    throw ex;
                else
                    throw new ArgumentNullException(name);
            }
        }
        /// <summary>
        /// Determines if a string value is not null, empty, or white space.
        /// </summary>
        /// <param name="value">Parameter value.</param>
        /// <returns>True when valid, e.g., not null, empty, or white space.</returns>
        public static bool IsValid(string value)
        {
            return IsValid<string>(value) && value.Trim().Length > 0;
        }

        /// <summary>
        /// Validates a reference type to ensure the value is not null, throwing exception when invalid.
        /// </summary>
        /// <typeparam name="T">Parameter type.</typeparam>
        /// <param name="name">Parameter name.</param>
        /// <param name="value">Parameter value.</param>
        /// <param name="ex">Exception to throw when invalid.</param>
        /// <exception cref="ArgumentNullException"/>
        public static void Validate<T>(string name, T value, Exception ex = null) where T : class
        {
            if (!IsValid<T>(value))
            {
                if (ex != null)
                    throw ex;
                else
                    throw new ArgumentNullException(name);
            }
        }

        /// <summary>
        /// Validates integer type to ensure the value is greater than 0.
        /// </summary>
        /// <param name="name">Parameter name.</param>
        /// <param name="value">Parameter value.</param>
        /// <param name="invalidValues">Invalid values.</param>
        /// <exception cref="ArgumentNullException"/>
        public static void Validate(string name, int value, params int[] invalidValues)
        {
            if (!IsValid(value, invalidValues))
                throw new ArgumentOutOfRangeException(name);
        }
        /// <summary>
        /// Validates integer type to ensure the value is either not 0 or not in the specified invalid values.
        /// </summary>
        /// <param name="value">Parameter value.</param>
        /// <param name="invalidValues">Invalid values.</param>
        /// <returns>True when value is either not 0 or not in the specified invalid values, otherwise false.</returns>
        public static bool IsValid(int value, params int[] invalidValues)
        {
            if ((invalidValues == null && value == 0) || (new List<int>(invalidValues).Contains(value)))
                return false;
            return true;
        }
        /// <summary>
        /// Determines that a reference type is not null.
        /// </summary>
        /// <typeparam name="T">Parameter type.</typeparam>
        /// <param name="value">Parameter value.</param>
        /// <returns>True when valid, e.g., not null</returns>
        public static bool IsValid<T>(T value) where T : class
        {
            if (value == null)
                return false;
            return true;
        }
        /// <summary>
        /// Verifies that<ul>
        /// <li>Destination is enum</li>
        /// <li>Destination underlying type is as expected, e.g., int, byte, etc.</li>
        /// <li>Source value is defined in the destination enum</li></ul>
        /// </summary>
        /// <typeparam name="TSource">Source value type</typeparam>
        /// <typeparam name="TDestinaion">Destination enum type</typeparam>
        /// <param name="enumValue">The value to convert to enum</param>
        /// <param name="qualifiedTargetTypeName">Expected underlying typename of the destination enum</param>
        /// <param name="validations">List of validations to perform, not seting it validates all</param>
        internal static void ValidateToEnum<TSource, TDestinaion>(TSource enumValue, string qualifiedTargetTypeName, params ToEnumValidationItem[] validations)
        {
            Type destinationType = typeof(TDestinaion);
            Array validationArray = validations;
            if (validations == null || validations.Length == 0)
                validationArray = Enum.GetValues(typeof(ToEnumValidationItem));
            Collection<ToEnumValidationItem> validationCollection = new Collection<ToEnumValidationItem>(validationArray as System.Collections.Generic.IList<ToEnumValidationItem>);

            // source is not empty
            if (validationCollection.Contains(ToEnumValidationItem.SourceNotEmpty) && !enumValue.GetType().IsValueType && (enumValue.ToString().Length == 0))
                throw new CustomException(string.Format(CultureInfo.CurrentCulture, StringResource.Default.Msg_AmbiguousSource, destinationType.FullName));
            // destination is enum
            ValidateEnum<TDestinaion>();
            // destination underlying type is as expected, e.g., int, byte, etc.
            if (validationCollection.Contains(ToEnumValidationItem.ExpectedUnderlyingDesinationType) && Enum.GetUnderlyingType(destinationType).FullName != qualifiedTargetTypeName)
                throw new CustomException(string.Format(CultureInfo.CurrentCulture, StringResource.Default.Msg_UnderlyingTypeMismatch, destinationType.FullName, enumValue));
            // source value is defined in the destination enum
            if (validationCollection.Contains(ToEnumValidationItem.SourceDefinedInDestinaion) && !Enum.IsDefined(destinationType, enumValue))
                throw new CustomException(string.Format(CultureInfo.CurrentCulture, StringResource.Default.Msg_UndefinedEnum, enumValue, destinationType.FullName));
        }
        /// <summary>
        /// Validates generic type to be enum.  Throws exception when invalid.
        /// </summary>
        /// <typeparam name="T">Generic type to validate for.</typeparam>
        public static void ValidateEnum<T>()
        {
            string msg;
            if (!IsValidEnum<T>(out msg))
                throw new CustomException(msg);
        }
        /// <summary>
        /// Determines if generic type is enum.
        /// </summary>
        /// <typeparam name="T">Type to validate.</typeparam>
        /// <param name="failureMessage">Formatted message to return when type is not enum.</param>
        /// <returns>True when generic type is enum, otherwise false.</returns>
        public static bool IsValidEnum<T>(out string failureMessage)
        {
            Type enumType = typeof(T);
            if (!enumType.IsEnum)
            {
                // destination is not enum
                failureMessage = string.Format(CultureInfo.CurrentCulture, StringResource.Default.Msg_TypeMismatch, enumType.FullName);
                return false;
            }
            failureMessage = "";
            return true;
        }
    }
}
